// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.logic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.Processor;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.dto.SysActionConst;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.eva.framework.utils.SNHelper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.x52im.rainbowchat.BaseConf;
import com.x52im.rainbowchat.cache.GroupsInfoCacheProvider;
import com.x52im.rainbowchat.cache.GroupsMembersCacheProvider;
import com.x52im.rainbowchat.cache.dto.GroupMember4Cache;
import com.x52im.rainbowchat.http.logic.dto.GroupEntity;
import com.x52im.rainbowchat.http.logic.dto.GroupMemberEntity;
import com.x52im.rainbowchat.im.impl.ChatLogicManager4Group;

/**
 * 群组相关的Http接口处理器实现类。
 * 
 * @author Json
 * @since 4.3
 */
public class GroupChatProcessor implements SysActionConst, Processor
{
	/**群组名称最大29个字符（中文算1个字符） */
	public final static int GROUP_NAME_MAX_LENGTH = 29;
	
	public static DBShell db = new DBShell();
	
	/**
	 * 处理器的核心实现方法.<br>
	 * 可据job_dispatch_id找到对应的JobDispatcher，JobDispatcher又能据
	 * action_id进行对应动作的处理，以完成本次操作请求.
	 * 
	 * @param job_dispatch_id 作业调度ID
	 * @param action_id 动作ID
	 * @param Object newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param Object oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param UserRoot user 包含该请求用户完整个人信息封装对象（该对象来自于成功登陆后存放于session中的值）
	 * @return 任意类型返回值
	 * @throws Exception
	 */
	@Override public Object process(int job_dispatch_id, int action_id, Object _newData, Object _oldData, User user)
			throws Exception
	{
		Object ret = null;
		String newDataJSON = (String)_newData;
		
		switch (job_dispatch_id)
		{	
			// 处理服务端对外提供的群基本信息管理等
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_GROUP_BASE_MGR:
				ret = maintainMgrGroupBaseJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
			// 处理服务端对外提供的群组相关信息查询等
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_GROUP_QUERY_MGR:
				ret = maintainMgrGroupQueryJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
			default:
				throw EndsExceptionFactory.INVALID_JOB_DISPATCHER_ID(job_dispatch_id);
		}
		return ret;
	}
	
	/** 
	 * 维护群组基本信息管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param userName 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainMgrGroupBaseJobDispatcher(int action_id, String newDataJSON
			, Object oldData, User user) throws Exception
	{
		Vector sqls = new Vector(2);
		Vector values = new Vector(2);
		
		switch (action_id)
		{
			// 【接口1016-24-7】创建群组的接口
			case ACTION_APPEND1:
			{	
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				// 群主（实际就是本次群的创建者）
				final String ownerUid = nwObj.getString("owner_uid"); 
				// 群主昵称（实际就是本次群的创建者）
				final String ownerNickName = nwObj.getString("owner_nickname");
				// 群成员
				String membersJSON = nwObj.getString("members");
				
				LoggerFactory.getLog().debug("==【【【【【【【正在创建群。。。】】】】】】】==");
				
				GroupEntity newGroupInfoReturn = null;
				
				final ArrayList<GroupMemberEntity> members = new Gson().fromJson(membersJSON
						, new TypeToken<ArrayList<GroupMemberEntity>>(){}.getType()); 
				
				if(members.size()>0)
				{
					// 新建的群id号
					String newGid = SNHelper.getSN("group_base_g_id");
					
					sqls.add("INSERT INTO group_base(g_id,g_status,g_name" +
							",g_owner_user_uid,create_user_uid,create_time)" +
							" VALUES(?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+")");
					values.add(new Object[]{newGid, "1"
							, constructNewGroupName(members)
							, ownerUid
							, ownerUid});
					
					// 插入群成员
					for(GroupMemberEntity m : members)
					{
						prepareInsertMemberToGroup(sqls, values, m.getUser_uid(), newGid, ownerUid);
					}
					
					// 更新群成员人数
					prepareUpdateGroupMemberCount(sqls, values, newGid);
					
					// ** 提交数据库处理
					LogicProcessor2.db.update(sqls, values, true);
					
//					1）写一个独立的方法，在建聊、删人、加人，且人数<=9时（写一个查询sql，取top9的人且头像文件不为空的情况），每次都重新生成建头像！
//					2）客户端每次都强制加载群头像（不要缓存了吧，省的麻类，像世界频道聊天一样，
//					       至少能保证及时拿到最新的群头像）！
					
					// 将本次创建成功的群基本信息查询出来（将返回给客户端使用）
					newGroupInfoReturn = this.queryGroupInfo(newGid, null);// 第2个参数null表示不需要取”我“在本群中的昵称，因为刚建的群还来不及设置群昵称呢
				
					// ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
					//                   建群、生成头像，因为这样会导致更多的服务端性消耗
					// 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
					GroupAvatarHelper.generateGroupAvatar(newGid);
					
					// ** 【高速缓存】：即时更新群缓存数据（因为新建了此群）
					GroupsInfoCacheProvider.getInstance().reloadOneFromDB(newGid);
					
					// ** 【实时通知本次群内的所有人】：群建立成功了
					ChatLogicManager4Group.batchNotification4CreateGroupAsync(
							ownerUid, ownerNickName, members, newGroupInfoReturn);
					
					// ** 【高速缓存】：将新群成员数据更新到高速缓存中（以便后绪使用）
					// TODO: 未来优化空间：直接用新群成员集合，可节省一次db查询，暂时可先不折腾
					GroupsMembersCacheProvider.getInstance().reloadOneFromDB(newGid);
				}
				
				//** 返回值(返回值“0”表示本次建群失败！)
				return newGroupInfoReturn==null?"0": JSON.toJSONString(newGroupInfoReturn);
			}
			// 【接口1016-24-8】修改群名称
			case ACTION_APPEND2:
			{	
				LoggerFactory.getLog().debug("==【【【【【【【正在修改群名。。。】】】】】】】==");
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String modify_by_uid = nwObj.getString("modify_by_uid");
				String modify_by_nickname = nwObj.getString("modify_by_nickname");
				String newGroupName = nwObj.getString("group_name");
				String gid = nwObj.getString("gid");
				
				sqls.add("UPDATE group_base SET g_name=? WHERE g_id=?");
				values.add(new Object[] {newGroupName, gid});
				boolean res = db.update(sqls, values, true);
				
				// ** 【高速缓存】：即时更新群缓存数据（因为新建了此群）
				GroupsInfoCacheProvider.getInstance().reloadOneFromDB(gid);
				
				// 【实时通知】：向群员发送一条通知（除修改者之外）
				ChatLogicManager4Group.batchNotification4GroupNameChangedAsync("修改群名"
						, modify_by_uid, modify_by_nickname, newGroupName
//						, this.queryGroupMemberList(gid)
						// 从高速缓存中读取群成员列表，提升性能
						, GroupsMembersCacheProvider.getInstance().get(gid).values()
						, gid);
				
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1016-24-9】修改"我"的群昵称
			case ACTION_APPEND3:
			{	
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);

				sqls.add("UPDATE group_members SET nickname_ingroup=? WHERE g_id=? and user_uid=?");
				values.add(new Object[] {nwObj.getString("nickname_ingroup")
						, nwObj.getString("gid"), nwObj.getString("user_uid")});
				boolean res = db.update(sqls, values, true);

				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1016-24-22】修改群公告（仅开放给群主）
			case ACTION_APPEND4:
			{	
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);

				String g_notice_updateuid = nwObj.getString("g_notice_updateuid");
				String g_id = nwObj.getString("g_id");
				
				String ret = "-1";    // 返回值：未定义
				
				if(!queryGroupOwner(g_id, g_notice_updateuid))
				{
					ret = "2";        // 返回值：修改人已不是群主，本次修改失败！
				}
				else
				{
					sqls.add("UPDATE group_base SET g_notice=?,g_notice_updatetime="+DBDepend.getDefaultDatetimeFunc()
							+",g_notice_updateuid=? WHERE g_id=? and g_owner_user_uid=?");
					values.add(new Object[] {nwObj.getString("g_notice"), g_notice_updateuid, g_id, g_notice_updateuid});
					boolean res = db.update(sqls, values, true);

					if(res)
						ret = "1";    // 返回值：本次修改成功
				}

				return ret;
			}
			// 【接口1016-24-23】删除群成员或退群 接口
			case ACTION_APPEND5:
			{	
				boolean sucess = false;
				try
				{
					LoggerFactory.getLog().debug("==【【【【【【【正在删除群成员或退群。。。】】】】】】】==");
					
					JSONObject nwObj = JSONObject.parseObject(newDataJSON);
					// 本次删除的操作人uid（群主踢人时本参数为群主，如果是用户自已退出退路时本参数为退出者自已）
					final String del_opr_uid = nwObj.getString("del_opr_uid"); 
					// 本次删除的操作人昵称
					final String del_opr_nickname = nwObj.getString("del_opr_nickname");
					// 本次删除发生的群id
					final String gid = nwObj.getString("gid");
//					// 本次删除发生的群名称
//					final String gname = nwObj.getString("gname");
					// 要删除的群成员
					String membersJSON = nwObj.getString("members");
					
					GroupEntity newGroupInfoReturn = null;
					
					final ArrayList<ArrayList> membersBeDelete = new Gson().fromJson(membersJSON
							, new TypeToken<ArrayList<ArrayList>>(){}.getType()); 
					
//					ArrayList<ArrayList> membersBeDelete = new Gson().fromJson(newDataJSON
//							, new TypeToken<ArrayList<ArrayList>>(){}.getType()); 
					
					// 要被删除的群员（用GroupMemberEntity组织的集合，调用其它方法时好用一些）
					ArrayList<GroupMemberEntity> membersBeDeleteList = new ArrayList<GroupMemberEntity>(); 
//					
					if(membersBeDelete != null && membersBeDelete.size() > 0)
					{
						for(ArrayList row : membersBeDelete)
						{
							// 以下字段及顺序请确保与客户端的接口请求保持一致！
//							String gid = (String)row.get(0);
							String userId = (String)row.get(1);
							String nickName = (String)row.get(2);
							
							// 将群员的基本信息存到一个集合中备用
							GroupMemberEntity gme = new GroupMemberEntity();
							gme.setUser_uid(userId);
							gme.setNickname(nickName);
							membersBeDeleteList.add(gme);
							
							sqls.add("delete from group_members where g_id=? and user_uid=? " +
							//## Bug FIX 20190330 START:
							//## 强制检查被删除者是否是群主，如果是群主，应该由它使用“解散群”而不是“退群”功能，否则上
							//## 会出现群主已经从群里退出的数据不一致情况（这种情况多发生于群主已转给“我”，但界面数据没
							//## 来的及刷新的情况下，以为还是普通群员，而点了“退群”按钮时）
									"	and user_uid <> (SELECT g_owner_user_uid FROM group_base WHERE g_id=?) ");
							//## Bug FIX 20190330 END:
							
							values.add(new Object[] {gid, userId, gid});
						}
					}
					
					// 更新群成员人数
					prepareUpdateGroupMemberCount(sqls, values, gid);
					
					db.update(sqls, values, false);
					sucess = true;
					
					// ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
					//                   删除群员并生成头像，因为这样会导致更多的服务端性消耗
					// 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
					GroupAvatarHelper.generateGroupAvatar(gid);
					
					// ** 【高速缓存】：将最新群成员数据更新到高速缓存中（以便后绪使用）
					// TODO: 未来优化空间：直接要删除的集合来更新缓存，可节省一次db查询，暂时可先不折腾
					GroupsMembersCacheProvider.getInstance().reloadOneFromDB(gid);
					
					// 【实时通知】：通知此次被删除的人、通知群里的其他余下的人
					ChatLogicManager4Group.batchNotification4SomeoneBeRemovedAsync(
							"删除群员", del_opr_uid, del_opr_nickname
							, membersBeDeleteList
//							, this.queryGroupMemberList(gid)
							// 从高速缓存中读取群成员列表，提升性能
							, GroupsMembersCacheProvider.getInstance().get(gid).values()
							, gid
//							, gname
							);
				}
				catch (Exception e)
				{
					LoggerFactory.getLog().debug(e.getMessage(), e);
				}

				return sucess?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1016-24-24】邀请入群/扫码入群/群名片入群接口
			case ACTION_APPEND6:
			{	
				LoggerFactory.getLog().debug("==【【【【【【【正在邀请入群群。。。】】】】】】】==");
				
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String srcFrom = nwObj.getString("src_from");              // 加群来源，"0"表示通过邀请加群、"1"表示通过扫描二难码加群、"2"表示通过分享的群名片加群，默认可为null（为null将默认是通过邀请加群）
				if(CommonUtils.isStringEmpty(srcFrom, true))
					srcFrom = "0";
				String inviteUid = nwObj.getString("invite_uid");          // 邀请发起人或分享者的uid
				String inviteNickName = nwObj.getString("invite_nickname");// 邀请发起人或分享者的昵称
				String inviteToGid = nwObj.getString("invite_to_gid");     // 邀请至群id
				String membersBeInviteJSON = nwObj.getString("members");   // 被邀请的成员

				// 新加群员列表
				ArrayList<GroupMemberEntity> newJoinedMemberList = new ArrayList<>();
				// 老群员列表
				Collection<GroupMember4Cache> oldMemberList = new ArrayList<>();
				
				ArrayList<ArrayList> membersBeInvite = new Gson().fromJson(membersBeInviteJSON, new TypeToken<ArrayList<ArrayList>>(){}.getType());
				if(!CommonUtils.isStringEmpty(inviteUid) && membersBeInvite != null && membersBeInvite.size() > 0) {
					// 查询老群员列表备用
//					oldMemberList = queryGroupMemberList(inviteToGid);
					// 从高速缓存中读取群成员列表，提升性能
					oldMemberList = GroupsMembersCacheProvider.getInstance().get(inviteToGid).values();
							
					// 持久化新群员
					for(ArrayList row : membersBeInvite) {
						// 以下字段及顺序请确保与客户端的接口请求保持一致！
						String gid = (String)row.get(0);
						String userId = (String)row.get(1);
						String nickname = (String)row.get(2);
						
						// 将群员的基本信息存到一个集合中备用
						GroupMemberEntity newGme = new GroupMemberEntity();
						newGme.setUser_uid(userId);
						newGme.setNickname(nickname);
						newJoinedMemberList.add(newGme);
						
						// 如果本次要邀请的人确实不在db中，则加入之（否则无需重复加入）
						if(db.queryData("select 1 from group_members where g_id='" +gid+"' and user_uid="+userId).size()<=0) {
							prepareInsertMemberToGroup(sqls, values, userId, gid, inviteUid);
						}
					}
				}

				// 更新群成员总人数
				prepareUpdateGroupMemberCount(sqls, values, inviteToGid);

				boolean res = db.update(sqls, values, false);
				
				// ** 【生成群头像】：暂时未使用异步方式，原因是希望阻止用户频繁
				//                   邀请群员并生成头像，因为这样会导致更多的服务端性消耗
				// 说明：放在通知前面生成头像，是希望在群员收到通知时，最新群头像早已生成完毕。
				GroupAvatarHelper.generateGroupAvatar(inviteToGid);
				
				// 【实时通知】：通知新加入的人（“他”加群成功）和老群员（有人加群了）
				ChatLogicManager4Group.batchNotification4InviteMembersAsync(
						srcFrom
						,"群聊CMD-邀请通知"
						, inviteUid, inviteNickName
						, newJoinedMemberList
						, oldMemberList
						, this.queryGroupInfo(inviteToGid, null));
				
				// ** 【高速缓存】：将最新群成员数据更新到高速缓存中（以便后绪使用）
				// TODO: 未来优化空间：直接用新进成员集合，可节省一次db查询，暂时可先不折腾
				GroupsMembersCacheProvider.getInstance().reloadOneFromDB(inviteToGid);
				
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			
			// 【接口1016-24-25】转让本群（仅开放给群主）接口
			case ACTION_APPEND7:
			{
				LoggerFactory.getLog().debug("==【【【【【【【正在转让群。。。】】】】】】】==");
				
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String oldOwnerUid = nwObj.getString("old_owner_uid");           // 原群主uid
//				String oldOwnerNickname = nwObj.getString("old_owner_nickname");      // 原群主昵称
				String newOwnerUid = nwObj.getString("new_owner_uid");           // 新群主uid（即将被转让为群主）
				String newOwnerNickname = nwObj.getString("new_owner_nickname"); // 新群主昵称
				String gid = nwObj.getString("g_id");                   // 转让发生的群
//				String gname = nwObj.getString("g_name");                    // 转让发生的群名
			
				String ret = "-1";    // 返回值：未定义
				
				if(!queryGroupOwner(gid, oldOwnerUid))
				{
					ret = "2";        // 返回值：转让发起人已不是群主，本次转让失败！
				}
				else
				{
					if(db.queryData("select 1 from group_members where user_uid='"
							+newOwnerUid+"' and g_id='"+gid+"'").size() <= 0)
					{
						ret = "3";    // 返回值：被转让人不是群成员，本次转让失败！
					}
					else
					{
						// 设置新群主
						sqls.add("update group_base set g_owner_user_uid=? where g_id=? and g_owner_user_uid=?");
						values.add(new Object[]{newOwnerUid, gid, oldOwnerUid});
						
						// 更新新群主成为群主的时间
						sqls.add("update group_members set be_owner_time="+DBDepend.getDefaultDatetimeFunc()
								+" where g_id=? and user_uid=?");
						values.add(new Object[]{gid, newOwnerUid});
						
						boolean res = db.update(sqls, values, false);
						if(res)
							ret = "1"; // 返回值：本次转让成功
						
						// 【实时通知】：向群员发送一条通知（除修改者之外）
						ChatLogicManager4Group.batchNotification4CommonInfoAsync("转让群主"
								, "\""+newOwnerNickname+"\"已成为新群主"
//								, this.queryGroupMemberList(gid)
								// 从高速缓存中读取群成员列表，提升性能
								, GroupsMembersCacheProvider.getInstance().get(gid).values()
								, gid
//								, gname
								// 原群主（也就是本次转让发起人）就不需要通知它自已了
								, oldOwnerUid
								);
					}
				}
				
				return ret;
			}
			// 【接口1016-24-26】解散群（仅开放给群主）接口
			case ACTION_APPEND8:
			{
				LoggerFactory.getLog().debug("==【【【【【【【正在解散群。。。】】】】】】】==");
				
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String ownerUid = nwObj.getString("owner_uid");           // 群主uid(也就是解散的发起人)
				String ownerNickname = nwObj.getString("owner_nickname"); // 群主昵称
				String gid = nwObj.getString("g_id");                     // 将要被解散的群
//				String gname = nwObj.getString("g_name");                 // 将要被解散的群名

				String ret = "-1"; // 返回值：未定义
				
				if(!queryGroupOwner(gid, ownerUid))
				{
					ret = "2";     // 返回值：解散发起人已不是群主，本次解散失败！
				}
				else
				{
					try
					{
						// 群解散前先取出所有群员列表（以备通知）
	//					ArrayList<GroupMember4Cache> allMembersBeforeDismiss = this.queryGroupMemberList(gid);
						// 从高速缓存中读取群成员列表，提升性能
						Collection<GroupMember4Cache> allMembersBeforeDismiss = 
								GroupsMembersCacheProvider.getInstance().get(gid).values();
						
						//** 先存档该群所有未超期的聊天记录（已超期的会自动存档哦）
						if(db.queryData("select 1 from missu_user_msgs_collect where chat_type=2 and dest_uid="
							+gid).size()>0)
						{
							LoggerFactory.getLog().debug("[群解解散时]该群存在未被存档的聊天记录，解散群时也将一并转储之...");
							
							// 先将聊天记录插入到存档表中
							sqls.add(TimmingRestoreMessages.COPY_CHATTING_MESSAGES_TO_ACHIVED_SQL+" where chat_type=2 and dest_uid=?");
							values.add(new Object[]{gid});
							
							// 再将聊天记录从聊天记录表中移除
							sqls.add(TimmingRestoreMessages.DELETE_CHATTING_MESSAGES_SQL+" where chat_type=2 and dest_uid=?");
							values.add(new Object[]{gid});
						}
						
						//** 先删除所有未被收取的本群离线消息（微信也是这样的）
						if(db.queryData("select 1 from missu_offline_history where group_id='"
							+gid+"'").size()>0)
						{
							LoggerFactory.getLog().debug("[群解解散时]该群存在未被拉取的离线消息，解散群时也将一并删除之...");
							
							sqls.add("delete from missu_offline_history where group_id=?");
							values.add(new Object[]{gid});
						}
						
						//** 再删除所有成员
						sqls.add("delete from group_members where g_id=?");
						values.add(new Object[]{gid});
						
						//** 再更新群基本信息中的状态字段
						// 补充说明：群解散时，将删除群成员、群友没有收取的离线聊天记录（微信也是这样），
						//          但不会删除群基本信息（只设状态为-1)，因为解散群的离线通知、公司
						//          的后台运营时，可能需要群的基本信息，用于提供基顾数据，所以不应把群
						//          基本信息也删除了。
	//					sqls.add("delete from group_base where g_id=?");
						sqls.add("update group_base set g_status=-1,del_user_uid=?,del_time="+DBDepend.getDefaultDatetimeFunc()
									+" where g_id=?");
						values.add(new Object[]{ownerUid, gid});
						
						// 更新群成员人数
						prepareUpdateGroupMemberCount(sqls, values, gid);
						
						boolean res = db.update(sqls, values, true);
						if(res)
							ret = "1"; // 返回值：本次解散成功
						
						// 【实时通知】：通知除群主外的所有人
						ChatLogicManager4Group.batchNotification4GroupDismissedAsync("群被解散"
								, allMembersBeforeDismiss, gid
	//							, gname
								, ownerUid, ownerNickname);
						
						// ** 【高速缓存】：从高速缓存中删除此群成员数据
	//					GroupsMembersCacheProvider.getInstance().reloadGroupFromDB(gid);
						GroupsMembersCacheProvider.getInstance().remove(gid);
						
						// ** 【高速缓存】：从高速缓存中删除此群（因为解散了此群）
	//					GroupsInfoCacheProvider.getInstance().reloadOneFromDB(gid);
						//# TODO: 为解决群解散时，离线APNS推送逻辑不能走完的bug而暂时注释了以下代码。
						/* 为了让上方离线消息的APNS推送代码更内聚，推送时需要的群名称等基本信息，
						是实时查高速缓存实现的，这样做的目的是提升离线消息处理代码的调用简单性。
						但带来的问题是，当以下代码执行时（即从高速缓存中去掉已解散的群基本信息），
						离线APNS推送逻辑就没法正常走完了（因为需要的群基本信息已不能从高速缓存中
						查到）。那么长此以往，被解散群的基本信息就留在内存里（直到下次重启）。
						解决办法可以是：为此缓存加一个解散时间，可以设定多长时间后，由单独的定时器
						来清楚，这样更合理。但实际上不清理，也占用不了多少内存，。单独维护清理逻辑
						反而得不偿失，可以先按现在的逻辑实现吧，以后进行高效架构拆分时再处理不迟。*/
//						GroupsInfoCacheProvider.getInstance().remove(gid);// comment by Jack Jiang 20190228
					}
					catch (Exception e)
					{
						LoggerFactory.getLog().warn("发生在解散群时：", e);
					}
				}

				return ret;
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护群组信息查询管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param userName 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainMgrGroupQueryJobDispatcher(int action_id, String newDataJSON
			, Object oldData, User user) throws Exception
	{
		Vector sqls = new Vector(2);
		Vector values = new Vector(2);
		
		switch (action_id)
		{
			// 【接口1016-25-7】获取用户群列表的接口
			case ACTION_APPEND1:
			{	
				// 客户端传过来的是UID
				String uid = newDataJSON;
//				// 该用户的好友群列表
				ArrayList<GroupEntity> groupsToClient = queryGroupInfoList(uid);
				
				return JSON.toJSONString(groupsToClient);
			}
			// 【接口1016-25-8】查询群基本信息
			case ACTION_APPEND2:
			{	
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);

				// 查询的群id
				String gid = nwObj.getString("gid");
				// 非必须参数，如果本参数不为空，则表示要同时把”我“在该群中的昵称给查出来，否则不需要查
				String myUserId = nwObj.getString("my_user_id");

				return JSON.toJSONString(queryGroupInfo(gid, myUserId));
			}
			// 【接口1016-25-9】查询群成员列表（含成员基本信息）
			case ACTION_APPEND3:
			{	
				// 查询的群id
				String gid = newDataJSON;
				return JSON.toJSONString(queryGroupMemberList(gid));
			}
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	private String constructNewGroupName(ArrayList<GroupMemberEntity> members)
	{
		if(members.size() > 0)
		{
			String name = "";
			
			int cnt = 0;
			for(int i=members.size()-1; i>=0; i--)
			{
				name += (cnt==0?"":"、")+members.get(i).getNickname();
				cnt += 1;

				// 最多只合成3个人的昵称作为群名
				if(cnt >= 3)
					break;
			}
			
			System.out.println("【截断前】生成完成的群聊名称："+name);
			name = CommonUtils.truncString(name, GROUP_NAME_MAX_LENGTH, "...");
			System.out.println("【截断后】生成完成的群聊名称："+name);
			
			return name+"等";
		}
		
		return "未命名的群聊";
	}
	
	/**
	 * 查询指定群组的群成员列表。
	 * <p>
	 * <b>注意：</b>如果用于大量频繁读取且只需要群成员id的场景下，请使用
	 * {@link GroupsMembersCacheProvider#get(String)}缓存，从而提升性能，
	 * 防止频繁读取DB这种慢IO拖慢系统性能且有可能导致DB瓶颈。
	 * 
	 * @param gid
	 * @see GroupsMembersCacheProvider#get(String)
	 */
	public static <T extends GroupMember4Cache> ArrayList<T> queryGroupMemberList(String gid) throws Exception
	{
		StringBuilder sb = new StringBuilder()
		.append("select ")
		.append("	a.user_uid " )
		.append("	,a.g_id ")
		.append("	,"+DBDepend.getNVLFunc("a.nickname_ingroup", "b.nickname")+" as nname ")
		.append("	,b.user_avatar_file_name ")
		.append(" FROM ")
		.append("(")
		// 使用子查询提升查询性能
		.append("	select user_uid,g_id,nickname_ingroup from group_members  where g_id='"+gid+"' ")
		.append(") a ")
		.append("left join missu_users b on a.user_uid=b.user_uid ")
		// 本条件将限制只能查到正常状态下的群成员列表（使用EXISTS语句作为条件是为了提升查询性能）
		.append("where EXISTS (select 1 from group_base c where a.g_id=c.g_id and c.g_status=1) ");

		// 群员列表数据库行数据
		Vector<Vector> members = db.queryData(sb.toString());
		ArrayList<GroupMember4Cache> membersToClient = new ArrayList<GroupMember4Cache>();
		// 遍历行数据，组装成对象List
		for(Vector row : members)
		{
			GroupMemberEntity gme = new GroupMemberEntity();
			int i = 0;
			gme.setUser_uid((String)row.get(i++));
			gme.setG_id((String)row.get(i++));
			gme.setNickname((String)row.get(i++));
			gme.setUserAvatarFileName((String)row.get(i++));
			
			membersToClient.add(gme);
		}
		
		return (ArrayList<T>)membersToClient;
	}
	
	/**
	 * 查询指定用户的所有群列表。
	 * <p>
	 * 注意：本方法中的sql查询字段个数、顺序，必须与解析方法 {@link #constructGroupEntityFromQueryRow(Vector)}
	 * 保持一致，否则无法完成从数据库原始行数据到 GroupEntity 对象的转换！
	 * 
	 * @param uid 被查询的用户id
	 * @return
	 * @throws Exception
	 * @see {@link #constructGroupEntityFromQueryRow(Vector)}
	 */
	private ArrayList<GroupEntity> queryGroupInfoList(String uid) throws Exception
	{
		StringBuilder sb = new StringBuilder();
		// 查询指定用户的群列表
		sb.append("select " )
			.append("	a.g_id" )              // 群id
			.append("	,b.g_status" )         // 群状态
			.append("	,b.g_name" )           // 群名称
			.append("	,b.g_owner_user_uid" ) // 群主uid
			.append("	,b.g_notice" )         // 群公告
//			.append("	,b.msg_no_promt" )     // 免打扰状态
			.append("	,b.max_member_count" ) // 允许的最大群员数
			.append("	,b.g_member_count" )   // 当前总群员数
			.append("	,c.nickname " )        // 群主昵称
			.append("	,a.nickname_ingroup " )// "我"在本群的昵称
			
			.append("	,b.g_notice_updatetime " )// 群公告更新时间
			.append("	,b.g_notice_updateuid " ) // 群公告更新者
			.append("	,d.nickname as nickname_ingroup " )   // 群公告更新者昵称
			.append("	,b.create_time " )        // 群创建时间
			.append("	,1 as isInGroup " )       // ”我“是否在此群中（既然是查我的群列表，那肯定在了，加此字段是为了与 queryGroupInfo()方法中的查询保持一致
			.append("	,e.nickname as create_user_name " )    // 群创建者昵称
			.append("from ")
			.append("( ")
			.append("	select user_uid,g_id,nickname_ingroup" +
					"	 from group_members where user_uid="+uid)
			.append(")a  ")
			// 注意：g_status=1表示此群当前是有效的（另外的状态是删除或被封禁，就不需要返回给用户了）
			.append("join group_base b on a.g_id=b.g_id and b.g_status=1 ")
			.append("left join missu_users c on b.g_owner_user_uid=c.user_uid ")
			.append("left join missu_users d on b.g_notice_updateuid=d.user_uid ")
			.append("left join missu_users e on b.create_user_uid=e.user_uid ")
			.append("order by b.create_time desc ");;
		
		// 该用户的好友群列表
		Vector<Vector> groups = db.queryData(sb.toString());
		ArrayList<GroupEntity> groupsToClient = new ArrayList<GroupEntity>();
		// 遍历群信息列表，组装成对象List
		for(Vector row : groups)
		{
			groupsToClient.add(constructGroupEntityFromQueryRow(row));
		}
		
		return groupsToClient;
	}
	
	/**
	 * 查询指定群的基本信息。
	 * <p>
	 * 注意：本方法中的sql查询字段个数、顺序，必须与解析方法 {@link #constructGroupEntityFromQueryRow(Vector)}
	 * 保持一致，否则无法完成从数据库原始行数据到 GroupEntity 对象的转换！
	 * 
	 * @param gid 要查询的群id
	 * @param myUserId 非必须参数，如果本参数不为空则表示要同时把”我“在该群中的昵称等给查出来，否则不需要查（表示普通的查询群信息）
	 * @return
	 * @throws Exception
	 * @see {@link #constructGroupEntityFromQueryRow(Vector)}
	 */
	private GroupEntity queryGroupInfo(String gid, String myUserId) throws Exception
	{
		// 是否需要取出”我“在该群中的信息（比如：群内昵称、我是否在此群中）
		boolean needGetMyselfInfo = !CommonUtils.isStringEmpty(myUserId);
		
		// 取出”我“在群里的昵称子查询
		String nickname_ingroup_SQLFeild = ",(select nickname_ingroup from group_members " +
				"	where g_id='"+gid+"' and user_uid='"+myUserId+"') as nickname_ingroup ";
		// 取出”我“是否在此群里的子查询（存在则返回1，不存在则返回0，返回-1表示不需要查询此值）
		String im_in_group_SQLField = ","+DBDepend.getNVLFunc(
				"(select 1 from group_members where g_id='"+gid+"' and user_uid='"+myUserId+"')", "0")
					+" as isInGroup ";
		
		StringBuilder sb = new StringBuilder();
		sb.append("select b.g_id")           // 群id
			.append("	,b.g_status")        // 群状态
			.append("	,b.g_name")          // 群名称
			.append("	,b.g_owner_user_uid")// 群主uid
			.append("	,b.g_notice")        // 群公告
//			.append("	,b.msg_no_promt")    // 免打扰状态
			.append("	,b.max_member_count")// 允许的最大群员数
			.append("	,b.g_member_count ") // 当前总群员数
			.append("	,c.nickname ")       // 群主昵称
			// "我"在本群的昵称
			.append(needGetMyselfInfo?nickname_ingroup_SQLFeild:"	,null as  nickname_ingroup ")
			
			.append("	,b.g_notice_updatetime " )// 群公告更新时间
			.append("	,b.g_notice_updateuid " ) // 群公告更新者
			.append("	,d.nickname as nickname_ingroup " )   // 群公告更新者昵称
			.append("	,b.create_time " )        // 群创建时间
			// 取出”我“是否在此群里的子查询（存在则返回1，不存在则返回0，返回-1表示不需要查询此值）
			.append(needGetMyselfInfo?im_in_group_SQLField:",-1 as isInGroup ")
			.append("	,e.nickname as create_user_name " )    // 群创建者昵称
			.append("from ")
			.append("(")
			.append("	select * from group_base where g_id='"+gid+"' and g_status=1 ")
			.append(")b ")
			.append("left join missu_users c on b.g_owner_user_uid=c.user_uid ")
			.append("left join missu_users d on b.g_notice_updateuid=d.user_uid ")
			.append("left join missu_users e on b.create_user_uid=e.user_uid ");

		Vector<Vector> vv = db.queryData(sb.toString());
		if(vv.size()>0)
			return constructGroupEntityFromQueryRow(vv.get(0));
		
		return null;
	}
	
	/**
	 * 解析数据库中查询出的群基本信息，并组装成对象，方便使用。
	 * <p>
	 * 注意：本方法中对数据库原始行数据的解析顺序、字段意义的改动，直接会影响到
	 * 方法 {@link #queryGroupInfo(String)}、{@link #queryGroupInfoList(String)}
	 * 期望的数据结果。所以如果改动本方法，则一定要对应的改动这两个方法！
	 * 
	 * @param row
	 * @return
	 * @see #queryGroupInfo(String)
	 * @see #queryGroupInfoList(String)
	 */
	private GroupEntity constructGroupEntityFromQueryRow(Vector row)
	{
		GroupEntity gee = new GroupEntity();
		gee.setG_id((String)row.get(0));
		gee.setG_status((String)row.get(1));
		gee.setG_name((String)row.get(2));
		gee.setG_owner_user_uid((String)row.get(3));
		gee.setG_notice((String)row.get(4));
//		gee.setMsg_no_promt((String)row.get(5));
		gee.setMax_member_count((String)row.get(5));
		gee.setG_member_count((String)row.get(6));
		gee.setG_owner_name((String)row.get(7));
		gee.setNickname_ingroup((String)row.get(8));
		
		gee.setG_notice_updatetime((String)row.get(9));
		gee.setG_notice_updateuid((String)row.get(10));
		gee.setG_notice_updatenick((String)row.get(11));
		
		gee.setCreate_time((String)row.get(12));
		gee.setImIsInGroup((String)row.get(13));
		gee.setCreate_user_name((String)row.get(14));
		
		return gee;
	}
	
	/**
	 * 加群预编译SQL.
	 * 
	 * @param sqls
	 * @param values
	 * @param uidBeInsert 被加入或邀请人UI
	 * @param gidBeInsert 被加入的群
	 * @param inviteUid 邀请加群的发起人
	 */
	private static void prepareInsertMemberToGroup(Vector sqls, Vector values
			, String uidBeInsert, String gidBeInsert, String inviteUid)
	{
		sqls.add("INSERT INTO group_members(user_uid,g_id,join_time,be_invite_user_id,msg_time_start" +
				") VALUES(?,?,"+DBDepend.getDefaultDatetimeFunc()+",?,"+DBDepend.getDefaultDatetimeFunc()+")");
		values.add(new Object[]{uidBeInsert, gidBeInsert,inviteUid});
	}
	
	/**
	 * 更新群内人数预编译SQL.
	 * 
	 * @param sqls
	 * @param values
	 * @param gid 要更新人数的群id
	 */
	private static void prepareUpdateGroupMemberCount(Vector sqls, Vector values, String gid)
	{
		sqls.add("update group_base set g_member_count=" +
				"(select count(*) from group_members where g_id=?) where g_id=?");
		values.add(new Object[]{gid, gid});
	}
	
	/**
	 * 从数据库中查询指定用户是否是该群群主。
	 * 
	 * @param gid 被查询的群
	 * @param theUidBeQuery 被查询的人
	 * @return
	 */
	private static boolean queryGroupOwner(String gid, String theUidBeQuery) throws Exception
	{
		return db.queryData("select 1 from group_base where g_owner_user_uid='"
				+theUidBeQuery+"' and g_id='"+gid+"'").size() > 0;
	}
}
